#include <QMap>
#include <QString>

#include "LeGerber/filereader.h"
#include "LeGerber/command.h"

#include "parsertest.h"

using namespace LeGerber;

parserTest::parserTest()
{
}

void parserTest::initTestCase()
{
}

void parserTest::cleanupTestCase()
{
}

void parserTest::testDefaultState()
{
	auto emptyStats = GerberCodeStatistics();
	auto emptyCommands = QList<Command *>();

	auto reader = new FileReader();
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);
	QCOMPARE(reader->codeCount(), 0);
	QCOMPARE(reader->takeCommands(), emptyCommands);
	QCOMPARE(reader->isValid(), true);
}

void parserTest::testEmptyDataIsValid()
{
	auto emptyStats = GerberCodeStatistics();
	auto emptyCommands = QList<Command *>();

	auto reader = new FileReader();
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);
	QCOMPARE(reader->codeCount(), 0);
	QCOMPARE(reader->takeCommands(), emptyCommands);
	QCOMPARE(reader->isValid(), true);
}

void parserTest::testReadCoordinateNumber_data()
{
	QTest::addColumn<int>("integralDigits");
	QTest::addColumn<int>("fractionalDigits");
	QTest::addColumn<QString>("input");
	QTest::addColumn<qreal>("output");
	QTest::newRow("1") << 1 << 1 << QString("0") << 0.0;
	QTest::newRow("2") << 1 << 1 << QString("1") << 0.1;
	QTest::newRow("3") << 1 << 1 << QString("-9") << -0.9;
	QTest::newRow("4") << 1 << 1 << QString("+9") << 0.9;
	QTest::newRow("5") << 1 << 1 << QString("00") << 0.0;
	QTest::newRow("6") << 1 << 1 << QString("+00") << 0.0;
	QTest::newRow("7") << 1 << 1 << QString("-00") << 0.0;
	QTest::newRow("8") << 1 << 1 << QString("12") << 1.2;
	QTest::newRow("9") << 1 << 1 << QString("+12") << 1.2;
	QTest::newRow("10") << 1 << 1 << QString("-21") << -2.1;
	QTest::newRow("11") << 2 << 6 << QString("0") << 0.0;
	QTest::newRow("12") << 2 << 6 << QString("-0") << 0.0;
	QTest::newRow("13") << 2 << 6 << QString("+0") << 0.0;
	QTest::newRow("14") << 2 << 6 << QString("1") << 0.000001;
	QTest::newRow("15") << 2 << 6 << QString("12") << 0.000012;
	QTest::newRow("16") << 2 << 6 << QString("123") << 0.000123;
	QTest::newRow("17") << 2 << 6 << QString("1234") << 0.001234;
	QTest::newRow("18") << 2 << 6 << QString("12345") << 0.012345;
	QTest::newRow("19") << 2 << 6 << QString("123456") << 0.123456;
	QTest::newRow("20") << 2 << 6 << QString("1234567") << 1.234567;
	QTest::newRow("21") << 2 << 6 << QString("12345678") << 12.345678;
}

void parserTest::testReadCoordinateNumber()
{
	QFETCH(int, integralDigits);
	QFETCH(int, fractionalDigits);
	QFETCH(QString, input);
	QFETCH(qreal, output);

	FileReaderContext context;
	context.coordinateNotation = AbsoluteCoordinateNotation;
	context.zeroBehaviour = LeadingZeroOmmited;
	context.coordinateIntegralDigits = integralDigits;
	context.coordinateFractionalDigits = fractionalDigits;

	QCOMPARE(context.readCoordinateNumber(input), output);
}

void parserTest::testReadCoordinate()
{
	FileReaderContext context;
	context.coordinateNotation = AbsoluteCoordinateNotation;
	context.zeroBehaviour = LeadingZeroOmmited;
	context.coordinateIntegralDigits = 2;
	context.coordinateFractionalDigits = 4;

	QCOMPARE(context.readCoordinate("123456", "654321"), QPointF(12.3456, 65.4321));
}

void parserTest::testParseMultiline_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<int>("yields");

	QTest::newRow("1") << QByteArray("") << 0;
	QTest::newRow("2") << QByteArray("\r\n") << 0;
	QTest::newRow("3") << QByteArray("\r\nG01*G02*\rG54D12*\n") << 4;
}

void parserTest::testParseMultiline()
{
	QFETCH(QByteArray, input);
	QFETCH(int, yields);

	FileReader reader;
	reader.setData(input);
	QVERIFY(reader.parse());
	QCOMPARE(reader.takeCommands().count(), yields);
}

void parserTest::testBasicGCodes()
{
	auto reader = new FileReader();
	reader->setData(QByteArray("G01*"
	                           "G02*"
	                           "G03*"
	                           "G04*"
	                           "G36*"
	                           "G37*"
	                           "G70*"
	                           "G71*"
	                           "G74*"
	                           "G75*"
	                           "G90*"
	                           "G91*"));
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 12);
	QCOMPARE(commands.at(0)->type(), int(SelectInterpolationCommand::Type));
	QCOMPARE(static_cast<SelectInterpolationCommand*>(commands.at(0))->interpolation, LinearInterpolation);
	QCOMPARE(commands.at(1)->type(), int(SelectInterpolationCommand::Type));
	QCOMPARE(static_cast<SelectInterpolationCommand*>(commands.at(1))->interpolation, ClockWiseInterpolation);
	QCOMPARE(commands.at(2)->type(), int(SelectInterpolationCommand::Type));
	QCOMPARE(static_cast<SelectInterpolationCommand*>(commands.at(2))->interpolation, CounterClockWiseInterpolation);
	QCOMPARE(commands.at(3)->type(), int(CommentCommand::Type));
	QCOMPARE(static_cast<CommentCommand*>(commands.at(3))->text, QString());
	QCOMPARE(commands.at(4)->type(), int(BeginRegionCommand::Type));
	QCOMPARE(commands.at(5)->type(), int(EndRegionCommand::Type));
	QCOMPARE(commands.at(6)->type(), int(SelectUnitCommand::Type));
	QCOMPARE(static_cast<SelectUnitCommand*>(commands.at(6))->unit, ImperialUnit);
	QCOMPARE(commands.at(7)->type(), int(SelectUnitCommand::Type));
	QCOMPARE(static_cast<SelectUnitCommand*>(commands.at(7))->unit, MetricUnit);
	QCOMPARE(commands.at(8)->type(), int(SelectQuadrantCommand::Type));
	QCOMPARE(static_cast<SelectQuadrantCommand*>(commands.at(8))->quadrant, SingleQuadrant);
	QCOMPARE(commands.at(9)->type(), int(SelectQuadrantCommand::Type));
	QCOMPARE(static_cast<SelectQuadrantCommand*>(commands.at(9))->quadrant, MultipleQuadrant);
	QCOMPARE(commands.at(10)->type(), int(SelectCoordinateNotationCommand::Type));
	QCOMPARE(static_cast<SelectCoordinateNotationCommand*>(commands.at(10))->notation, AbsoluteCoordinateNotation);
	QCOMPARE(commands.at(11)->type(), int(SelectCoordinateNotationCommand::Type));
	QCOMPARE(static_cast<SelectCoordinateNotationCommand*>(commands.at(11))->notation, IncrementalCoordinateNotation);
}

void parserTest::testBasicMCodes()
{
	auto reader = new FileReader();
	reader->setData(QByteArray("M00*"
	                           "M01*" // Ignored
	                           "M02*"));
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	qDebug() << reader->warnings();
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 3);
	QCOMPARE(commands.at(0)->type(), int(EndCommand::Type));
	QCOMPARE(commands.at(1)->type(), int(NullCommand::Type));
	QCOMPARE(commands.at(2)->type(), int(EndCommand::Type));
}

void parserTest::testBasicDCodes()
{
	auto reader = new FileReader();
	reader->setData(QByteArray("D01*"
	                           "D02*"
	                           "D03*"
	                           "D11*"
	                           "D2147483647*"
	                           //"D1*"
	                           //"D2*"
	                           //"D3*"
	                           ));
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	qDebug() << reader->warnings();
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 5);
	QCOMPARE(commands.at(0)->type(), int(InterpolateCommand::Type));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->xCoordinate));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->yCoordinate));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->xDistance));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->yDistance));
	QCOMPARE(commands.at(1)->type(), int(MoveCommand::Type));
	QVERIFY(qIsNaN(static_cast<MoveCommand*>(commands.at(1))->xCoordinate));
	QVERIFY(qIsNaN(static_cast<MoveCommand*>(commands.at(1))->yCoordinate));
	QCOMPARE(commands.at(2)->type(), int(FlashCommand::Type));
	QVERIFY(qIsNaN(static_cast<FlashCommand*>(commands.at(2))->xCoordinate));
	QVERIFY(qIsNaN(static_cast<FlashCommand*>(commands.at(2))->yCoordinate));
	QCOMPARE(commands.at(3)->type(), int(SelectApertureCommand::Type));
	QCOMPARE(static_cast<SelectApertureCommand*>(commands.at(3))->apertureNumber, 11ull);
	QCOMPARE(commands.at(4)->type(), int(SelectApertureCommand::Type));
	QCOMPARE(static_cast<SelectApertureCommand*>(commands.at(4))->apertureNumber, 2147483647ull);

	//    QCOMPARE(reader->commands().at(5)->type(), int(InterpolateCommand::Type));
	//    QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(reader->commands().at(5))->xCoordinate));
	//    QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(reader->commands().at(5))->yCoordinate));
	//    QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(reader->commands().at(5))->xDistance));
	//    QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(reader->commands().at(5))->yDistance));
	//    QCOMPARE(reader->commands().at(6)->type(), int(MoveCommand::Type));
	//    QVERIFY(qIsNaN(static_cast<MoveCommand*>(reader->commands().at(6))->xCoordinate));
	//    QVERIFY(qIsNaN(static_cast<MoveCommand*>(reader->commands().at(6))->yCoordinate));
	//    QCOMPARE(reader->commands().at(7)->type(), int(FlashCommand::Type));
	//    QVERIFY(qIsNaN(static_cast<FlashCommand*>(reader->commands().at(7))->xCoordinate));
	//    QVERIFY(qIsNaN(static_cast<FlashCommand*>(reader->commands().at(7))->yCoordinate));
}

// FIXME: We assume a default of FSLAX25Y25
// TODO: use data vector
void parserTest::testNominalD01()
{
	auto reader = new FileReader();
	reader->setData(QByteArray("X123D01*"
	                           "X123Y321D01*"
	                           "X123Y321I98D01*"
	                           "X123Y321I98J89D01*"
	                           "Y321D01*"
	                           "Y321I98D01*"
	                           "Y321I98J89D01*"
	                           "X123J89D01*"));
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 8);
	QCOMPARE(commands.at(0)->type(), int(InterpolateCommand::Type));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(0))->xCoordinate, 0.00123);
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->yCoordinate));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->xDistance));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->yDistance));
	QCOMPARE(commands.at(1)->type(), int(InterpolateCommand::Type));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(1))->xCoordinate, 0.00123);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(1))->yCoordinate, 0.00321);
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(1))->xDistance));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(1))->yDistance));
	QCOMPARE(commands.at(2)->type(), int(InterpolateCommand::Type));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(2))->xCoordinate, 0.00123);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(2))->yCoordinate, 0.00321);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(2))->xDistance, 0.00098);
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(2))->yDistance));
	QCOMPARE(commands.at(3)->type(), int(InterpolateCommand::Type));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(3))->xCoordinate, 0.00123);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(3))->yCoordinate, 0.00321);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(3))->xDistance, 0.00098);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(3))->yDistance, 0.00089);
	QCOMPARE(commands.at(4)->type(), int(InterpolateCommand::Type));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(4))->xCoordinate));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(4))->yCoordinate, 0.00321);
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(4))->xDistance));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(4))->yDistance));
	QCOMPARE(commands.at(5)->type(), int(InterpolateCommand::Type));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(5))->xCoordinate));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(5))->yCoordinate, 0.00321);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(5))->xDistance, 0.00098);
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(5))->yDistance));
	QCOMPARE(commands.at(6)->type(), int(InterpolateCommand::Type));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(6))->xCoordinate));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(6))->yCoordinate, 0.00321);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(6))->xDistance, 0.00098);
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(6))->yDistance, 0.00089);
	QCOMPARE(commands.at(7)->type(), int(InterpolateCommand::Type));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(7))->xCoordinate, 0.00123);
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(7))->yCoordinate));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(7))->xDistance));
	QCOMPARE(static_cast<InterpolateCommand*>(commands.at(7))->yDistance, 0.00089);
}

void parserTest::testNominalD02()
{
	auto reader = new FileReader();
	reader->setData(QByteArray("X123D02*"
	                           "X123Y321D02*"
	                           "Y321D02*"));
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 3);
	QCOMPARE(commands.at(0)->type(), int(MoveCommand::Type));
	QCOMPARE(static_cast<MoveCommand*>(commands.at(0))->xCoordinate, 0.00123);
	QVERIFY(qIsNaN(static_cast<MoveCommand*>(commands.at(0))->yCoordinate));
	QCOMPARE(commands.at(1)->type(), int(MoveCommand::Type));
	QCOMPARE(static_cast<MoveCommand*>(commands.at(1))->xCoordinate, 0.00123);
	QCOMPARE(static_cast<MoveCommand*>(commands.at(1))->yCoordinate, 0.00321);
	QCOMPARE(commands.at(2)->type(), int(MoveCommand::Type));
	QVERIFY(qIsNaN(static_cast<MoveCommand*>(commands.at(2))->xCoordinate));
	QCOMPARE(static_cast<MoveCommand*>(commands.at(2))->yCoordinate, 0.00321);
}

void parserTest::testNominalD03()
{
	auto reader = new FileReader();
	reader->setData(QByteArray("X123D03*"
	                           "X123Y321D03*"
	                           "Y321D03*"));
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 3);
	QCOMPARE(commands.at(0)->type(), int(FlashCommand::Type));
	QCOMPARE(static_cast<FlashCommand*>(commands.at(0))->xCoordinate, 0.00123);
	QVERIFY(qIsNaN(static_cast<FlashCommand*>(commands.at(0))->yCoordinate));
	QCOMPARE(commands.at(1)->type(), int(FlashCommand::Type));
	QCOMPARE(static_cast<FlashCommand*>(commands.at(1))->xCoordinate, 0.00123);
	QCOMPARE(static_cast<FlashCommand*>(commands.at(1))->yCoordinate, 0.00321);
	QCOMPARE(commands.at(2)->type(), int(FlashCommand::Type));
	QVERIFY(qIsNaN(static_cast<FlashCommand*>(commands.at(2))->xCoordinate));
	QCOMPARE(static_cast<FlashCommand*>(commands.at(2))->yCoordinate, 0.00321);
}

void parserTest::testNominalDnn_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<quint32>("apertureNumer");

	QTest::newRow("1") << QByteArray("D10*") << 10u;
	QTest::newRow("2") << QByteArray("D12345*") << 12345u;
	QTest::newRow("3") << QByteArray("D2147483647*") << 2147483647u;
}

void parserTest::testNominalDnn()
{
	QFETCH(QByteArray, input);
	QFETCH(quint32, apertureNumer);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 1);
	QCOMPARE(commands.at(0)->type(), int(SelectApertureCommand::Type));
	QCOMPARE(static_cast<SelectApertureCommand*>(commands.at(0))->apertureNumber, apertureNumer);
}

void parserTest::testNominalG54Dnn_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<quint32>("apertureNumer");

	QTest::newRow("1") << QByteArray("G54D10*") << 10u;
	QTest::newRow("2") << QByteArray("G54D12345*") << 12345u;
	QTest::newRow("3") << QByteArray("G54D2147483647*") << 2147483647u;
}

void parserTest::testNominalG54Dnn()
{
	QFETCH(QByteArray, input);
	QFETCH(quint32, apertureNumer);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	qDebug() << reader->warnings();
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 2);
	QCOMPARE(commands.at(0)->type(), int(NullCommand::Type));
	QCOMPARE(commands.at(1)->type(), int(SelectApertureCommand::Type));
	QCOMPARE(static_cast<SelectApertureCommand*>(commands.at(1))->apertureNumber, apertureNumer);
}

void parserTest::testNominalStandaloneCoordinates_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<bool>("xnan");
	QTest::addColumn<qreal>("xvalue");
	QTest::addColumn<bool>("ynan");
	QTest::addColumn<qreal>("yvalue");
	QTest::newRow("1") << QByteArray("%FSLAX25Y25*%X11298Y-064*") << false << .11298 << false << -0.00064;
	QTest::newRow("2") << QByteArray("%FSLAX25Y25*%Y047*") << true << .0 << false << 0.00047;
	QTest::newRow("3") << QByteArray("%FSLAX25Y25*%X3*") << false << .00003 << true << .0;
	QTest::newRow("4") << QByteArray("%FSLAX25Y25*%Y0*") << true << .0 << false << 0.0;
	QTest::newRow("3") << QByteArray("%FSLAX25Y25*%X-374*") << false << -0.00374 << true << .0;
	QTest::newRow("3") << QByteArray("%FSLAX23Y23*%X652*") << false << 0.652 << true << .0;
}

void parserTest::testNominalStandaloneCoordinates()
{
	QFETCH(QByteArray, input);
	QFETCH(bool, xnan);
	QFETCH(qreal, xvalue);
	QFETCH(bool, ynan);
	QFETCH(qreal, yvalue);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	qDebug() << reader->warnings();
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 1); // FIXME: %FS will be as well a command
	QCOMPARE(commands.at(0)->type(), int(InterpolateCommand::Type));

	QCOMPARE(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->xCoordinate), xnan);
	if (!xnan)
		QCOMPARE(static_cast<InterpolateCommand*>(commands.at(0))->xCoordinate, xvalue);

	QCOMPARE(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->yCoordinate), ynan);
	if (!ynan)
		QCOMPARE(static_cast<InterpolateCommand*>(commands.at(0))->yCoordinate, yvalue);

	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->xDistance));
	QVERIFY(qIsNaN(static_cast<InterpolateCommand*>(commands.at(0))->yDistance));
}

void parserTest::testComposedGCodes_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<int>("precode");
	QTest::addColumn<int>("postcode");
	QTest::newRow("1") << QByteArray("G01D01*") << int(SelectInterpolationCommand::Type) << int(InterpolateCommand::Type);
	QTest::newRow("2") << QByteArray("G01X321D02*") << int(SelectInterpolationCommand::Type) << int(MoveCommand::Type);
	QTest::newRow("3") << QByteArray("G02X0Y0D01*") << int(SelectInterpolationCommand::Type) << int(InterpolateCommand::Type);
	QTest::newRow("4") << QByteArray("G02X0Y0D02*") << int(SelectInterpolationCommand::Type) << int(MoveCommand::Type);
	QTest::newRow("5") << QByteArray("G03Y321D01*") << int(SelectInterpolationCommand::Type) << int(InterpolateCommand::Type);
	QTest::newRow("6") << QByteArray("G03Y321D02*") << int(SelectInterpolationCommand::Type) << int(MoveCommand::Type);
	// Regression
	QTest::newRow("7") << QByteArray("%FSLAX46Y46*%G03X144780000Y-119380000I2540000J0D01*") << int(SelectInterpolationCommand::Type) << int(InterpolateCommand::Type);
}

void parserTest::testComposedGCodes()
{
	QFETCH(QByteArray, input);
	QFETCH(int, precode);
	QFETCH(int, postcode);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 2);
	QCOMPARE(commands.at(0)->type(), precode);
	QCOMPARE(commands.at(1)->type(), postcode);

}

void parserTest::testNominalFSCode_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<int>("intDigits");
	QTest::addColumn<int>("fracDigits");
	QTest::addColumn<int>("coordinateMode");
	QTest::addColumn<int>("zeroBehaviour");

	QTest::newRow("1") << QByteArray("%FSLAX10Y10*%") << 1 << 0 << int(AbsoluteCoordinateNotation) << int(LeadingZeroOmmited);
	QTest::newRow("2") << QByteArray("%FSLAX11Y11*%") << 1 << 1 << int(AbsoluteCoordinateNotation) << int(LeadingZeroOmmited);
	QTest::newRow("3") << QByteArray("%FSLAX36Y36*%") << 3 << 6 << int(AbsoluteCoordinateNotation) << int(LeadingZeroOmmited);
	// Non statndard
	// FSAX24Y24
	// FSDAX23Y23
	// FSLX24Y24
	// FSTAX44Y44
}

void parserTest::testNominalFSCode()
{
	QFETCH(QByteArray, input);
	QFETCH(int, intDigits);
	QFETCH(int, fracDigits);
	QFETCH(int, coordinateMode);
	QFETCH(int, zeroBehaviour);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	QCOMPARE(reader->coordinateIntegralDigits(), intDigits);
	QCOMPARE(reader->coordinateFractionalDigits(), fracDigits);
	QCOMPARE(reader->coordinateNotation(), CoordinateNotation(coordinateMode));
	QCOMPARE(reader->zeroBehaviour(), ZeroBehaviour(zeroBehaviour));

	// TODO: check for command
}

void parserTest::testNominalMOCode_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<int>("unit");

	QTest::newRow("IN") << QByteArray("%MOIN*%") << int(ImperialUnit);
	QTest::newRow("MM") << QByteArray("%MOMM*%") << int(MetricUnit);
}

void parserTest::testNominalMOCode()
{
	QFETCH(QByteArray, input);
	QFETCH(int, unit);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 1);
	QCOMPARE(commands.at(0)->type(), int(SelectUnitCommand::Type));
	QCOMPARE(static_cast<SelectUnitCommand*>(commands.at(0))->unit, Unit(unit));
}

void parserTest::testNominalADCode_data()
{
	typedef QList<qreal> ParameterList;
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<int>("number");
	QTest::addColumn<QString>("name");
	QTest::addColumn<ParameterList>("parameters");

	QTest::newRow("1") << QByteArray("%ADD10C*%") << 10 << QString("C") << ParameterList();
	QTest::newRow("2") << QByteArray("%ADD10C,.025*%") << 10 << QString("C") << (ParameterList() << 0.025);
	QTest::newRow("3") << QByteArray("%ADD20R*%") << 20 << QString("R") << (ParameterList());
	QTest::newRow("4") << QByteArray("%ADD20R,.050X.050X.027*%") << 20 << QString("R") << (ParameterList() << 0.05 << 0.05 << 0.027);
	QTest::newRow("5") << QByteArray("%ADD570O*%") << 570 << QString("O") << (ParameterList());
	QTest::newRow("6") << QByteArray("%ADD570O,.030X.040X.015*%") << 570 << QString("O") << (ParameterList() << 0.03 << 0.04 << 0.015);
	QTest::newRow("7") << QByteArray("%ADD123456789P,.016X6*%") << 123456789 << QString("P") << (ParameterList() << 0.016 << 6.0);
	QTest::newRow("8") << QByteArray("%ADD10C,2.3622e-006*%") << 10 << QString("C") << (ParameterList() << 2.3622e-006);
	// Regression
	// Non-standard: Name has ' ' and '/'
	QTest::newRow("9") << QByteArray("%ADD13VB_Custom_Obstruct Trace/Via Round 5.0 Both,0.00000X1.00000X0.00000*%")
	                   << 13 << QString("VB_Custom_Obstruct Trace/Via Round 5.0 Both")
	                   << (ParameterList() << 0.0 << 1.0 << 0.0);
	// Non-standard: Name has '-'
	QTest::newRow("10") << QByteArray("%ADD16RR-H1744980-W3048000-R872490-RO1.500*%")
	                    << 16 << QString("RR-H1744980-W3048000-R872490-RO1.500")
	                    << (ParameterList());
	// Non-standard: Name has '~'
	QTest::newRow("10") << QByteArray("%ADD137RECTHERMD27~~~*%")
	                    << 137 << QString("RECTHERMD27~~~")
	                    << (ParameterList());
	// Non-standard: modifier list has extra spaces
	QTest::newRow("11") << QByteArray("%ADD33MTHOLE, 0.0640 X0.0440 X0.0050 X0.0*%")
	                    << 33 << QString("MTHOLE")
	                    << (ParameterList() << 0.064 << 0.044 << 0.005 << 0.0);
	QTest::newRow("12") << QByteArray("%ADD10C, 2.700*%")
	                    << 10 << QString("C")
	                    << (ParameterList() << 2.7);
	// Non-standard?: no digit after '.'
	QTest::newRow("13") << QByteArray("%ADD10C,3.*%")
	                    << 10 << QString("C")
	                    << (ParameterList() << 3.0);

	//
	// ADD125R,0.03260.326* (Missing X)
	// ADD14C,0.1X* (Trailing X)
	// ADD20R, 0.800X0.900* (LEading space)
}

void parserTest::testNominalADCode()
{
	typedef QList<qreal> ParameterList;
	QFETCH(QByteArray, input);
	QFETCH(int, number);
	QFETCH(QString, name);
	QFETCH(ParameterList, parameters);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 1);
	QCOMPARE(commands.at(0)->type(), int(AddApertureCommand::Type));
	QCOMPARE(static_cast<AddApertureCommand*>(commands.at(0))->apertureNumber, number);
	QCOMPARE(static_cast<AddApertureCommand*>(commands.at(0))->templateName, name);
	QCOMPARE(static_cast<AddApertureCommand*>(commands.at(0))->parameters, parameters);
}

// AddApertureTemplateCommand: QString templateName; QList<AperturePrimitive> primitives;
// AperturePrimitive: int type; bool exposure; QList<qreal> modifiers;
typedef QList<AperturePrimitive> AperturePrimitiveList;
void parserTest::testNominalAMCode_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<QString>("templateName");
	QTest::addColumn<AperturePrimitiveList>("primitives");

	QTest::newRow("1") << QByteArray("%AMname*%")
	                   << QString("name")
	                   << AperturePrimitiveList();

	QTest::newRow("2") << QByteArray("%AMSQUAREWITHHOLE*"
	                                 "21,1,10,10,0,0,0*"
									 "1,0,5,0,0*%")
	                   << QString("SQUAREWITHHOLE")
	                   << (AperturePrimitiveList()
	                       << AperturePrimitive(21, true, QList<qreal>() << 10.0 << 10.0 << 0.0 << 0.0 << 0.0)
	                       << AperturePrimitive(1, false, QList<qreal>() << 5.0 << 0.0 << 0.0));
	QTest::newRow("2") << QByteArray("%AMSQUAREWITHHOLE*\r"
	                                 "21,1,10,10,0,0,0*\r\n"
									 "1,0,5,0,0*\n%")
	                   << QString("SQUAREWITHHOLE")
	                   << (AperturePrimitiveList()
	                       << AperturePrimitive(21, true, QList<qreal>() << 10.0 << 10.0 << 0.0 << 0.0 << 0.0)
	                       << AperturePrimitive(1, false, QList<qreal>() << 5.0 << 0.0 << 0.0));
}

void parserTest::testNominalAMCode()
{
	QFETCH(QByteArray, input);
	QFETCH(QString, templateName);
	QFETCH(AperturePrimitiveList, primitives);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 1);
	QCOMPARE(commands.at(0)->type(), int(AddApertureTemplateCommand::Type));
	QCOMPARE(static_cast<AddApertureTemplateCommand*>(commands.at(0))->templateName, templateName);
	QCOMPARE(static_cast<AddApertureTemplateCommand*>(commands.at(0))->primitives, primitives);
}

void parserTest::testNominalLPCode_data()
{
	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<int>("polarity");

	QTest::newRow("C") << QByteArray("%LPC*%") << int(ClearPolarity);
	QTest::newRow("D") << QByteArray("%LPD*%") << int(DarkPolarity);
}

void parserTest::testNominalLPCode()
{
	QFETCH(QByteArray, input);
	QFETCH(int, polarity);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 1);
	QCOMPARE(commands.at(0)->type(), int(SelectPolarityCommand::Type));
	QCOMPARE(static_cast<SelectPolarityCommand*>(commands.at(0))->polarity, Polarity(polarity));
}

void parserTest::testNominalSRCode_data()
{

}

void parserTest::testNominalSRCode()
{

}

void parserTest::testNominalTACode_data()
{

	QTest::addColumn<QByteArray>("input");
	QTest::addColumn<QString>("name");
	QTest::addColumn<QStringList>("values");

	QTest::newRow("1") << QByteArray("%TANAME*%") << QString("NAME") << QStringList();
	QTest::newRow("2") << QByteArray("%TAA,B,C*%") << QString("A") << (QStringList() << QString("B") << QString("C"));
	QTest::newRow("3") << QByteArray("%TA.FileFunction,SolderMask,Top*%") << QString(".FileFunction") << (QStringList() << QString("SolderMask") << QString("Top"));
}

void parserTest::testNominalTACode()
{
	QFETCH(QByteArray, input);
	QFETCH(QString, name);
	QFETCH(QStringList, values);

	auto reader = new FileReader();
	reader->setData(input);
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 1);
	QCOMPARE(commands.at(0)->type(), int(SetApertureAttributeCommand::Type));
	QCOMPARE(static_cast<SetApertureAttributeCommand*>(commands.at(0))->name, name);
	QCOMPARE(static_cast<SetApertureAttributeCommand*>(commands.at(0))->values, values);
}

void parserTest::testNominalTFCode_data()
{

	// Non standard?
	// github.com/librecube-space/LC-3103/production/electrical/gerber/flatsat_board-In1_Cu.g2:
	// TF.GenerationSoftware,KiCad,Pcbnew,0.201509241831+6216~30~ubuntu14.04.1-product*
}

void parserTest::testNominalTFCode()
{

}

void parserTest::testNominalTDCode_data()
{

}

void parserTest::testNominalTDCode()
{

}

void parserTest::testNominalASCode_data()
{

}

void parserTest::testNominalASCode()
{

}

void parserTest::testNominalINCode_data()
{

}

void parserTest::testNominalINCode()
{

}

void parserTest::testNominalIPCode_data()
{

}

void parserTest::testNominalIPCode()
{

}

void parserTest::testNominalIRCode_data()
{

}

void parserTest::testNominalIRCode()
{

}

void parserTest::testNominalLNCode_data()
{

}

void parserTest::testNominalLNCode()
{

}

void parserTest::testNominalMICode_data()
{

}

void parserTest::testNominalMICode()
{

}

void parserTest::testNominalOFCode_data()
{

}

void parserTest::testNominalOFCode()
{

}

void parserTest::testNominalSFCode_data()
{

}

void parserTest::testNominalSFCode()
{

}

void parserTest::testSimpleApertureAttributeCodes()
{
	auto reader = new FileReader();
	reader->setData(QByteArray("%TFSIMPLENAME*%"
	                           "%TASIMPLENAME*%"
	                           "%TDSIMPLENAME*%"));
	QCOMPARE(reader->parse(), true);
	QCOMPARE(reader->isValid(), true);
	QCOMPARE(reader->errorString(), QString("No error"));
	QCOMPARE(reader->warningCount(), 0);

	auto commands = reader->takeCommands();
	QCOMPARE(commands.count(), 3);
	QCOMPARE(commands.at(0)->type(), int(SetFileAttributeCommand::Type));
	QCOMPARE(static_cast<SetFileAttributeCommand*>(commands.at(0))->name, QString("SIMPLENAME"));
	QCOMPARE(commands.at(1)->type(), int(SetApertureAttributeCommand::Type));
	QCOMPARE(static_cast<SetApertureAttributeCommand*>(commands.at(1))->name, QString("SIMPLENAME"));
	QCOMPARE(commands.at(2)->type(), int(ClearApertureAttributeCommand::Type));
	QCOMPARE(static_cast<ClearApertureAttributeCommand*>(commands.at(2))->name, QString("SIMPLENAME"));
}

QTEST_MAIN(parserTest)

